// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.IO;
using System.Collections.Generic;
using System.Xml.Linq;
using CIP4.Common;
using CIP4.Simple;
using com.mosso.cloudfiles;
using com.mosso.cloudfiles.domain;
using com.mosso.cloudfiles.exceptions;

namespace CIP4.DataProvider.Mosso
{
    public class DataProvider : Simple.DataProvider, IDataProvider
    {
        readonly Connection m_Connection;

        IDataProvider Cache { get; set; }

        #region IDataProvider Members

        public string ConnectionString { get; private set; }

        public DataProvider (CIP4ConnectionAttribute DataProviderConnection, IDataProvider cachingProvider, LoggerDelegate logger)
            : base (DataProviderConnection)
        {
            Cache = cachingProvider;
            if (!(DataProviderConnection is CIP4MossoConnectionAttribute))
                LoggerWrapper(MajorEnum.Error, MinorEnum.UnexpectedConnectionType,
                    "{0} is not an acceptable connection type", new object[] { DataProviderConnection.ToString() }, logger);
            else
            {
                LoggerWrapper(MajorEnum.Trace, MinorEnum.InitializingProvider,
                    "Initalizing Provider: {0}", new object[] { DataProviderConnection }, logger);
                CIP4MossoConnectionAttribute connection = (CIP4MossoConnectionAttribute)DataProviderConnection;
                ConnectionString = connection.ToString();
                m_Connection = new Connection(new UserCredentials(connection.UserName, connection.AccessKey));
            }
        }

        public void DeleteCollection(CollectionType collection, LoggerDelegate logger)
        {
            if (!LoggerWrapper(MajorEnum.Trace, MinorEnum.ActionStart,
                "Started {0}", new object[] { MinorEnum.DeleteCollection }, logger))
                return;

            try
            {
                CF_Container container = GetContainer(collection);
                Dictionary<GetItemListParameters, string> parameters = new Dictionary<GetItemListParameters, string>();
                string[] itemIds = container.GetObjectNames(parameters);
                foreach (string itemExternalID in itemIds)
                {
                    container.DeleteObject(itemExternalID);
                }
            }
            catch (Exception ex)
            {
                LoggerWrapper(MajorEnum.Error, MinorEnum.ErrorDeletingCollection,
                    "Unexpected exception deleting collection {0}: {1}", new object[] { collection.Name, ex }, logger);
            }
            LoggerWrapper(MajorEnum.Information, MinorEnum.DeleteCollection,
                "Collection deleted {0}", new object[] { collection }, logger);
        }

        public override IEnumerable<object> GetItems(CollectionType collection, IBooleanValue selectionExpression, CursorType cursor)
        {
            List<object> items = new List<object>();
            if (!LoggerWrapper(MajorEnum.Trace, MinorEnum.ActionStart,
                "Started {0}", new object[] { MinorEnum.GetItems }, cursor.Logger))
                return items;

            try
            {
                CF_Container container = GetContainer(collection);

                ISerializerType serializer = cursor.GetSerializer(SerializerKindEnum.CIP4Xml);

                string lookForExternalId = GetExternalIdValueFromSelectionExpression(selectionExpression, serializer);

                if (!string.IsNullOrEmpty(lookForExternalId))
                {
                    // if we can find it in the cache, no point in looking in Mosso, as an id has 
                    // been specified on the given template item. 
                    if (Cache != null)
                    {
                        foreach (object item in Cache.GetItems(collection, selectionExpression, cursor))
                        {
                            items.Add(item);
                            return items;
                        }
                    }
                    {
                        StorageItem sitem = m_Connection.GetStorageItem(collection.ExternalIDString(), lookForExternalId);
                        StreamReader rdr = new StreamReader(sitem.ObjectStream);
                        object item = serializer.GetObjectForData(XElement.Parse(rdr.ReadToEnd()));
                        if (CompareProperties(selectionExpression, item, serializer))
                            items.Add(item);
                        if (Cache != null)
                            Cache.SaveItems(items, collection, cursor.GetSerializer);
                    }
                }
                else
                {
                    if (cursor.FailOnLinearSearch)
                        if (!LoggerWrapper(MajorEnum.Error, MinorEnum.FailOnLinearSearch,
                            "Failed on linear search accessing {0}", new object[] { collection }, cursor.Logger))
                            return items;


                    Dictionary<GetItemListParameters, string> parameters = new Dictionary<GetItemListParameters, string>();
                    if (!string.IsNullOrEmpty(cursor.Marker))
                        parameters.Add(GetItemListParameters.Marker, cursor.Marker);
                    if (cursor.PageSize > 0)
                        parameters.Add(GetItemListParameters.Limit, cursor.PageSize.ToString());
                    string[] itemIds = container.GetObjectNames(parameters);
                    foreach (string itemExternalID in itemIds)
                    {
                        StorageItem sitem = m_Connection.GetStorageItem(collection.ExternalIDString(), itemExternalID);
                        StreamReader rdr = new StreamReader(sitem.ObjectStream);
                        object item = serializer.GetObjectForData(XElement.Parse(rdr.ReadToEnd()));
                        if (CompareProperties(selectionExpression, item, serializer))
                        {
                            items.Add(item);
                            cursor.Marker = itemExternalID;
                            if (items.Count > cursor.PageSize)
                                break;
                        }
                    }
                    if (Cache != null)
                        Cache.SaveItems(items, collection, cursor.GetSerializer);
                }
                return items;
            }
            catch (StorageItemNotFoundException)
            {
                return items;
            }
            finally
            {
                LoggerWrapper(MajorEnum.Information, MinorEnum.GetItems,
                    "Get {0} items from {1}", new object[] { items.Count, collection }, cursor.Logger);
            }
        }

        public override void PurgeItems(IEnumerable<ExternalIDType> externalIDs, CollectionType collection, GetSerializerDelegate getSerializer)
        {
            ISerializerType serializer = getSerializer(SerializerKindEnum.CIP4Xml);

            if (!LoggerWrapper(MajorEnum.Trace, MinorEnum.ActionStart,
                "Started {0}", new object[] { MinorEnum.PurgeItems }, serializer.Logger))
                return;

            if (!ContainerExists(collection))
            {
                LoggerWrapper(MajorEnum.Warning, MinorEnum.NothingDeleted, "Nothing deleted from {0}", new object[] { collection.Name }, serializer.Logger);
                return;
            }
            CF_Container container = GetContainer(collection);
            int deletedCount = 0;
            int deleteAttempts = 0;
            foreach (ExternalIDType externalID in externalIDs)
            {
                deleteAttempts++;
                try
                {
                    container.DeleteObject(externalID.ToString());
                    LoggerWrapper(MajorEnum.Trace, MinorEnum.ItemDeleted,
                        "deleted item {0}", new object[] { externalID }, serializer.Logger);
                    deletedCount += 1;
                }
                catch (StorageItemNotFoundException)
                {
                }
            }

            if (Cache != null)
                Cache.PurgeItems(externalIDs, collection, getSerializer);

            if (deletedCount == 0)
                LoggerWrapper(MajorEnum.Warning, MinorEnum.NothingDeleted, "Nothing deleted from {0}", new object[] { collection.Name }, serializer.Logger);

            LoggerWrapper(MajorEnum.Trace, MinorEnum.ItemsActuallyPurged,
                "{0} items actually deleted from {1}", new object[] { deletedCount, collection.Name }, serializer.Logger);
            LoggerWrapper(MajorEnum.Information, MinorEnum.PurgeItems,
                "{0} items deleted from {1}", new object[] { deleteAttempts, collection.Name }, serializer.Logger);

        }

        public void SaveItems(IEnumerable<object> items, CollectionType collection, GetSerializerDelegate getSerializer)
        {
            ISerializerType serializer = getSerializer(SerializerKindEnum.CIP4Xml);

            if (!LoggerWrapper(MajorEnum.Trace, MinorEnum.ActionStart,
                "Started {0}", new object[] { MinorEnum.SaveItems }, serializer.Logger))
                return;

            CF_Container container = GetContainer(collection);
            int savedCount = 0;
            foreach (object objectItem in items)
            {
                if (!SetHeaderProperties(objectItem, collection, serializer))
                    continue;

                StreamWriter wrtr = new StreamWriter(new MemoryStream());
                wrtr.Write(serializer.GetDataForObject(objectItem).ToString());
                wrtr.Flush();
                wrtr.BaseStream.Position = 0;
                container.AddObject(wrtr.BaseStream, serializer.GetExternalID(objectItem).ToString());
                savedCount++;
                LoggerWrapper(MajorEnum.Trace, MinorEnum.ItemsUpdated, "Items {0} updated", new object[] { serializer.GetExternalID(objectItem) }, serializer.Logger);
            }

            if (Cache != null)
                Cache.SaveItems(items, collection, getSerializer);

            LoggerWrapper(MajorEnum.Information, MinorEnum.SaveItems,
                "{0} items saved in {1}", new object[] { savedCount, collection.Name }, serializer.Logger);
        }

        public void Terminate(LoggerDelegate logger)
        {
        }

        #endregion

        #region Helper Functions

        private readonly Dictionary<string, CF_Container> m_Containers = new Dictionary<string, CF_Container>();

        private CF_Container GetContainer(CollectionType collection)
        {
            if (m_Containers.ContainsKey(collection.ExternalIDString()))
                return m_Containers[collection.ExternalIDString()];
            try
            {
                m_Connection.GetContainerInformation(collection.ExternalIDString());
            }
            catch (ContainerNotFoundException)
            {
                m_Connection.CreateContainer(collection.ExternalIDString());
            }

            CF_Container container = new CF_Container(m_Connection, collection.ExternalIDString());
            m_Containers.Add(collection.ExternalIDString(), container);
            return container;
        }

        private bool ContainerExists(CollectionType collection)
        {
            if (m_Containers.ContainsKey(collection.ExternalIDString()))
                return true;
            try
            {
                m_Connection.GetContainerInformation(collection.ExternalIDString());
                return true;
            }
            catch (ContainerNotFoundException)
            {
                return false;
            }
        }
        #endregion
    }
}
